package net.beaconcontroller.comp416project.core;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import javax.swing.Timer;

import net.beaconcontroller.comp416project.data.DataFlowMod;
import net.beaconcontroller.comp416project.model.Model;
import net.beaconcontroller.core.IBeaconProvider;
import net.beaconcontroller.core.IOFMessageListener;
import net.beaconcontroller.core.IOFSwitch;
import net.beaconcontroller.core.IOFSwitchListener;
import net.beaconcontroller.packet.Ethernet;

import org.openflow.protocol.OFFlowMod;
import org.openflow.protocol.OFMatch;
import org.openflow.protocol.OFMessage;
import org.openflow.protocol.OFPacketIn;
import org.openflow.protocol.OFPacketOut;
import org.openflow.protocol.OFPort;
import org.openflow.protocol.OFStatisticsRequest;
import org.openflow.protocol.OFType;
import org.openflow.protocol.action.OFAction;
import org.openflow.protocol.action.OFActionEnqueue;
import org.openflow.protocol.action.OFActionOutput;
import org.openflow.protocol.statistics.OFFlowStatisticsReply;
import org.openflow.protocol.statistics.OFFlowStatisticsRequest;
import org.openflow.protocol.statistics.OFStatistics;
import org.openflow.protocol.statistics.OFStatisticsType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
 * 
 * @author SHI ZHONGQI
 * @author JI ZHIHAO
 * Modified from the tutorial provided by David Erickson (daviderickson@cs.stanford.edu)
 *
 */
public class SwitchCore implements IOFMessageListener, IOFSwitchListener{
	final static int time_delay=500;
    protected static Logger log = LoggerFactory.getLogger(SwitchCore.class);
    protected IBeaconProvider beaconProvider;
    protected Timer timer = null;
    protected Model model=null;
    int test=0;
    int bufferID;
    HashMap<Integer,Boolean> singleLink=new HashMap<Integer, Boolean>();
    protected Map<IOFSwitch, Map<Long,Short>> macTables =
        new HashMap<IOFSwitch, Map<Long,Short>>();

    public Command receive(IOFSwitch sw, OFMessage msg) throws IOException {
        initMACTable(sw);
        OFPacketIn pi = (OFPacketIn) msg;
        if(timer==null)
        	startAnimation(sw);
        if(model==null)
        	model=new Model(sw);
        /**
         * This is the layer 2 based switching you will create. Once you have
         * created the appropriate code in the forwardAsLearningSwitch method
         * (see below), comment out the above call to forwardAsHub, and
         * uncomment the call here to forwardAsLearningSwitch.
         */
        test++;
//        forwardAsHub(sw, pi);
        forwardAsLearningSwitch(sw, pi);
        return Command.CONTINUE;

    }

    /**
     * Floods the packet out all switch ports except the port it
     * came in on.
     *
     * @param sw the OpenFlow switch object
     * @param pi the OpenFlow Packet In object
     * @throws IOException
     */
    public void forwardAsHub(IOFSwitch sw, OFPacketIn pi) throws IOException {
        // Create the OFPacketOut OpenFlow object
        OFPacketOut po = new OFPacketOut();

        // Create an output action to flood the packet, put it in the OFPacketOut
        OFAction action = new OFActionOutput(OFPort.OFPP_FLOOD.getValue());
        po.setActions(Collections.singletonList(action));
        // Set the port the packet originally arrived on
        po.setInPort(pi.getInPort());
        
        // Reference the packet buffered at the switch by id
        po.setBufferId(pi.getBufferId());
        if (pi.getBufferId() == OFPacketOut.BUFFER_ID_NONE) {
            /**
             * The packet was NOT buffered at the switch, therefore we must
             * copy the packet's data from the OFPacketIn to our new
             * OFPacketOut message.
             */
            po.setPacketData(pi.getPacketData());
        }
        // Send the OFPacketOut to the switch
        sw.getOutputStream().write(po);
    }

    /**
     * Learn the source MAC:port pair for each arriving packet, and send packets
     * out the port previously learned for the destination MAC of the packet,
     * if it exists.  Otherwise flood the packet similarly to forwardAsHub.
     * @param sw the OpenFlow switch object
     * @param pi the OpenFlow Packet In object
     * @throws IOException
     */
    int identifier=0,noIdent=0;;
    public void forwardAsLearningSwitch(IOFSwitch sw, OFPacketIn pi) throws IOException {
        Map<Long,Short> macTable = macTables.get(sw);
       
        OFMatch match = OFMatch.load(pi.getPacketData(), pi.getInPort());
        
        // Learn the port to reach the packet's source MAC
        macTable.put(Ethernet.toLong(match.getDataLayerSource()), pi.getInPort());

      
        // Retrieve the port previously learned for the packet's dest MAC
        Short outPort = macTable.get(Ethernet.toLong(match.getDataLayerDestination()));
      
        if (outPort != null) {
            // Destination port known, push down a flow
        	
            OFFlowMod fm = new OFFlowMod();
            fm.setBufferId(pi.getBufferId());
            Model.setBufferied(pi.getBufferId());
            fm.setCommand(OFFlowMod.OFPFC_MODIFY);
            // Time out the flow after 5 seconds if inactivity
            fm.setIdleTimeout((short) 0);
            fm.setHardTimeout((short) 0);
            // Match the packet using the match created above
            fm.setMatch(match);
            // Send matching packets to outPort
            System.out.println("Modeified test : "+test);
            ArrayList<OFAction> list = new ArrayList<OFAction>();
            OFAction action = new OFActionOutput(outPort);
            list.add(action);
            if(test>10)
            {
            	OFAction action1 = new OFActionOutput((short) 1);
            	list.add(action1);
            	fm.setCommand(OFFlowMod.OFPFC_DELETE);
            	if(test>30)
            		test=0;
            }
            if(match.getTransportDestination() == 22){
//            	if(!singleLink.containsKey(match.getNetworkDestination())||!singleLink.get(match.getNetworkDestination())){
//            		singleLink.put(match.getNetworkDestination(), true);
//            		fm.setActions(list);
//            	}
//            	else{
	        		fm.setActions(null);
	        		System.out.println("You shall not pass!");
//            	}
        	}
            else
            	fm.setActions(list);
            // Send this OFFlowMod to the switch
           // System.out.println(fm.toString());
            sw.getOutputStream().write(fm);

            if (pi.getBufferId() == OFPacketOut.BUFFER_ID_NONE) {
                /**
                 * EXTRA CREDIT: This is a corner case, the packet was not
                 * buffered at the switch so it must be sent as an OFPacketOut
                 * after sending the OFFlowMod
                 */
                OFPacketOut po = new OFPacketOut();
                action = new OFActionOutput(outPort);
                po.setActions(Collections.singletonList(action));
                po.setBufferId(OFPacketOut.BUFFER_ID_NONE);
                po.setInPort(pi.getInPort());
                po.setPacketData(pi.getPacketData());
                sw.getOutputStream().write(po);
            }
        } else {
            // Destination port unknown, flood packet to all ports	
        		forwardAsHub(sw, pi);

        }
    }

    /**
     * Ensure there is a MAC to port table per switch
     * @param sw
     */
    private void initMACTable(IOFSwitch sw) {
        Map<Long,Short> macTable = macTables.get(sw);
        if (macTable == null) {
            macTable = new HashMap<Long,Short>();
            macTables.put(sw, macTable);
        }
    }

    @Override
    public void addedSwitch(IOFSwitch sw) {
    }

    @Override
    public void removedSwitch(IOFSwitch sw) {
        macTables.remove(sw);
    }

    /**
     * @param beaconProvider the beaconProvider to set
     */
    public void setBeaconProvider(IBeaconProvider beaconProvider) {
        this.beaconProvider = beaconProvider;
    }

    public void startUp() {
        log.trace("Starting");
        beaconProvider.addOFMessageListener(OFType.PACKET_IN, this);
        beaconProvider.addOFSwitchListener(this);
    }

    public void shutDown() {
        log.trace("Stopping");
        beaconProvider.removeOFMessageListener(OFType.PACKET_IN, this);
        beaconProvider.removeOFSwitchListener(this);
    }

    public String getName() {
        return "Comp416 Project ZHIHAO&Larry";
    }

    /*Here we add our own function*/
    public void coreStop(){
		timer.stop();
	}
	
	public void startAnimation(final IOFSwitch sw) {
		  
	     timer = new Timer(time_delay, new ActionListener() {
	            @Override
            public void actionPerformed(ActionEvent e) {
	            	OFStatisticsRequest req = new OFStatisticsRequest();
	                OFFlowStatisticsRequest fsr = new OFFlowStatisticsRequest();
	                OFMatch match = new OFMatch();
	                match.setWildcards(0xffffffff);
	                fsr.setMatch(match);
	                fsr.setOutPort(OFPort.OFPP_NONE.getValue());
	                fsr.setTableId((byte) 0xff);
	                req.setStatisticType(OFStatisticsType.FLOW);
	                req.setStatistics(fsr);
	                req.setLengthU(req.getLengthU() + fsr.getLength());
	                Future<List<OFStatistics>> future;
	                List<OFStatistics> values = null;
	                if (sw != null) {
	                    try {
	                        future = sw.getStatistics(req);
	                        values = future.get(10000, TimeUnit.SECONDS);
	                        sw.getFeaturesReply().getPorts();
	                        Iterator<OFStatistics> i = values.iterator();
	                        HashMap<String, OFFlowMod> flowMap = new HashMap<String,OFFlowMod>(); 
	                        HashMap<OFFlowMod, String> stringMap = new HashMap<OFFlowMod, String>(); 
	                       // System.out.println("**********Here will be the flow table*************");
	                        if(!values.isEmpty()){
	                        	//System.out.println("***********************************************");
	                        	int k = 1;
	                        	
	                        	//Iterate the flowtable to form the hashmap<FlowName, OFFlowMod>
	                        	while(i.hasNext())
                        		{
	                        		// Cystal: ((OFFlowStatisticsReply) i.next()).getMatch();
	                        		OFFlowStatisticsReply ofrep = (OFFlowStatisticsReply) i.next();
	                        	
	                        		String flowName = "flow" + k;
	                        		DataFlowMod dfm = new DataFlowMod();
	                        		k++;
	                        		dfm.setMatch((ofrep).getMatch());
	                        		
	                        		dfm.setActions(ofrep.getActions());
	                        		dfm.setCommand(OFFlowMod.OFPFC_MODIFY)
	                        		.setHardTimeout((ofrep).getHardTimeout())
	                        		.setIdleTimeout((ofrep).getIdleTimeout())
	                        		.setPriority((ofrep).getPriority());
//	                        		dfm.setFlowByte((ofrep).getByteCount());
//	                        		dfm.setFlowPacket((ofrep).getPacketCount());
//	                        		dfm.setTimeNow(ofrep.getDurationSeconds());
	                        		flowMap.put(flowName, dfm);
	                        		stringMap.put(dfm, flowName);
	                        		
	                        		//System.out.println((k-1)+" "+ofrep.toString());
                        		}
	                        	if(!flowMap.isEmpty())
	                        		Model.setFlowMap(flowMap,stringMap);
	                        	else
	                        		Model.setFlowMap();
	                        	
	                        	//System.out.println("***********************************************");
	                        }
	                        	
	                    } catch (Exception e1) {
	                        log.error("Failure retrieving " + "flow information", e1);
	                    }
	                }
	          
	                
	            }
	            });
	    timer.start();
	} 
    
	public static void upadteFlowMap(IOFSwitch sw){
		OFStatisticsRequest req = new OFStatisticsRequest();
        OFFlowStatisticsRequest fsr = new OFFlowStatisticsRequest();
        OFMatch match = new OFMatch();
        match.setWildcards(0xffffffff);
        fsr.setMatch(match);
        fsr.setOutPort(OFPort.OFPP_NONE.getValue());
        fsr.setTableId((byte) 0xff);
        req.setStatisticType(OFStatisticsType.FLOW);
        req.setStatistics(fsr);
        req.setLengthU(req.getLengthU() + fsr.getLength());
        Future<List<OFStatistics>> future;
        List<OFStatistics> values = null;
        if (sw != null) {
            try {
                future = sw.getStatistics(req);
                values = future.get(10000, TimeUnit.SECONDS);
                sw.getFeaturesReply().getPorts();
                Iterator<OFStatistics> i = values.iterator();
                HashMap<String, OFFlowMod> flowMap = new HashMap<String, OFFlowMod>(); 
                HashMap<OFFlowMod, String> stringMap = new HashMap<OFFlowMod, String>(); 
               // System.out.println("**********Here will be the flow table*************");
                if(!values.isEmpty()){
                	//System.out.println("***********************************************");
                	int k = 1;
                	
                	//Iterate the flowtable to form the hashmap<FlowName, OFFlowMod>
                	while(i.hasNext())
            		{
                		// Cystal: ((OFFlowStatisticsReply) i.next()).getMatch();
                		OFFlowStatisticsReply ofrep = (OFFlowStatisticsReply) i.next();
                	
                		String flowName = "flow" + k;
                		DataFlowMod dfm = new DataFlowMod();
                		k++;
                		dfm.setMatch((ofrep).getMatch());
                		
                		dfm.setActions(ofrep.getActions());
                		dfm.setCommand(OFFlowMod.OFPFC_MODIFY)
                		.setHardTimeout((ofrep).getHardTimeout())
                		.setIdleTimeout((ofrep).getIdleTimeout())
                		.setPriority((ofrep).getPriority());
//                		dfm.setFlowByte((ofrep).getByteCount());
//                		dfm.setFlowPacket((ofrep).getPacketCount());
//                		dfm.setTimeNow(ofrep.getDurationSeconds());
                		flowMap.put(flowName, dfm);
                		stringMap.put(dfm, flowName);
                		
                		//System.out.println((k-1)+" "+ofrep.toString());
            		}
                	if(!flowMap.isEmpty())
                		{
                		Model.setFlowMap(flowMap,stringMap);
                		
                		}
                	else
                		Model.setFlowMap();
                	
                	//System.out.println("***********************************************");
                }
                	
            } catch (Exception e1) {
                log.error("Failure retrieving " + "flow information", e1);
            }
        }
  
	}
	
	//hello
	public static void modifiedTheFlow(IOFSwitch sw, OFFlowMod fm) throws IOException{
		 sw.getOutputStream().write(fm);
	}

}
